package helper.scxml.scxml2.t0.case2_traffic.zone

import helper.base.A2LHM
import helper.base.BaseTypeHelper.toIntRange
import helper.base.LHMHelper.A2LHMExpand.addIfAbsent
import helper.base.LHMHelper.A2LHMExpand.makeA2LHM
import helper.base.LHMHelper.A3LHM.Companion.makeA3LHM
import helper.base.TupleHelper.GetTuple.getA2Tuple
import helper.base.ZoneConstraintHelper.CoeffVar.Companion.makeCoeffVar
import helper.base.ZoneConstraintHelper.Constraint
import helper.base.ZoneConstraintHelper.PointConstraint.Companion.makePointConstraint
import helper.base.ZoneConstraintHelper.ZoneConstraint
import helper.base.ZoneConstraintHelper.ZoneConstraint.Companion.makeZoneConstraint
import helper.base.debug.DebugHelper.DebuggerList
import helper.scxml.scxml2.EnvHelper.RunResult
import helper.scxml.scxml2.t0.case2_traffic.zone.TrafficEnvHelper.StrategyObj.Ren.aUniformRenStrategy
import helper.scxml.scxml2.t0.case2_traffic.zone.TrafficEnvHelper.StrategyObj.aCarEasy
import helper.scxml.scxml2.t0.case2_traffic.zone.TrafficEnvHelper.StrategyObj.aTrain
import helper.scxml.scxml2.t0.case2_traffic.zone.TrafficEnvHelper.StrategyObj.getSimpleT1EnvStrategy
import helper.scxml.scxml2.zone.t2.ConfigHelper.Config
import helper.scxml.scxml2.zone.t2.ConfigHelper.Config.Companion.makeConfig
import helper.scxml.scxml2.zone.t2.NoteRunResultFun
import helper.scxml.scxml2.zone.t2.ZoneEnvHelper.AssignAction
import helper.scxml.scxml2.zone.t2.ZoneEnvHelper.AssignAction.Companion.makeAssignAction
import helper.scxml.scxml2.zone.t2.ZoneEnvHelper.ConstraintGetter.Companion.makeConstraintGetter
import helper.scxml.scxml2.zone.t2.ZoneEnvHelper.ConstraintGetterArg.Companion.makeConstraintGetterArg
import helper.scxml.scxml2.zone.t2.ZoneEnvHelper.DoubleDoubleFunTuple
import helper.scxml.scxml2.zone.t2.ZoneEnvHelper.Edge.Companion.makeEdge
import helper.scxml.scxml2.zone.t2.ZoneEnvHelper.IStrategyEx
import helper.scxml.scxml2.zone.t2.ZoneEnvHelper.StringActionListLHM.Companion.makeStringActionListLHM
import helper.scxml.scxml2.zone.t2.ZoneEnvHelper.ZoneEnv
import helper.scxml.scxml2.zone.t2.ZoneEnvHelper.equal_name_StrStrBoolFun
import helper.scxml.scxml2.zone.t2.ZoneEnvHelper.txNoteRunResultFun
import helper.scxml.scxml2.zone.t2.ZoneLearningHelper.IDsDFun
import helper.scxml.strategy.StrategyHelper.TimedEventHelper.ITimedEvent
import helper.scxml.strategy.StrategyHelper.TimedEventHelper.TimedEvent
import helper.scxml.strategy.TotalStrategyHelper.KREWTuple.Companion.makeKREWTuple
import helper.scxml.strategy.TotalStrategyHelper.LeafSNode
import helper.scxml.strategy.sNodeUnit.T2SNodeUnitHelper.ERT2SNodeUnit.Companion.makeL0ERT2SNodeUnit
import helper.scxml.strategy.sNodeUnit.T2SNodeUnitHelper.SubT2SNodeUnit
import helper.scxml.strategy.sNodeUnit.T2SNodeUnitHelper.SubT2SNodeUnit.Companion.makeSubT2SNodeUnit
import helper.scxml.strategy.sNodeUnit.T2SNodeUnitHelper.T2SNodeUnit

object TrafficEnvHelper {
    class TrafficEnv(override val timeNeedEnd: Int = 60) : ZoneEnv() {
        companion object {
            fun makeTrafficEnv(timeNeedEnd: Number = 60) = TrafficEnv(timeNeedEnd.toInt())

            val timeIfBad = 10000.0
        }

        val finalLocationList: ArrayList<String> by lazy {
            arrayListOf("s9")
        }

        override fun isOnFinalLocation(nowZS: Config): Boolean {
            return finalLocationList.contains(
                nowZS.getState0String()
            )
        }

        override fun isEnd(nowZS: Config, nextMinEventTime: Int): Boolean {
            require(nextMinEventTime >= 0)
            val nowT = nowZS.byNameFindC(tClock).getPointD()

            val isAtFinalLocation = isOnFinalLocation(nowZS)
            val isTimeExceededAfterNextEvent = nowT + nextMinEventTime > timeNeedEnd
            val isTimeCloseToEndButNotInEasyCar = nowT > timeNeedEnd - 20 && nowZS.getState0String() != aCarEasy
            val isTimeVeryCloseToEndInTrain = nowT > timeNeedEnd - 35 - 4 && nowZS.getState0String() == aTrain

            return isAtFinalLocation || isTimeExceededAfterNextEvent || isTimeCloseToEndButNotInEasyCar || isTimeVeryCloseToEndInTrain
        }


        //边
        override val aStateEventStateLHM = makeA3LHM<String, String, String>().also { lhm ->
            arrayOf(
                makeEdge("s0", "s1"),
                makeEdge("s0", "s2"),
                makeEdge("s0", "s5"),
                makeEdge("s1", "s9"),
                makeEdge("s2", "s3"),
                makeEdge("s2", "s4"),
                makeEdge("s3", "s9"),
                makeEdge("s4", "s9"),
                makeEdge("s5", "s6"),
                makeEdge("s5", "s7"),
                makeEdge("s6", "s9"),
                makeEdge("s7", "s5"),
                makeEdge("s7", "s8"),
                makeEdge("s8", "s0")
            ).forEach {
                lhm.addIfAbsent(
                    it.locationStart, it.edgeName, it.locationEnd
                )
            }
        }

        override val stateStayMaxLHM by lazy {
            makeA2LHM<String, Int>().also { lhm ->
                arrayOf(
                    getA2Tuple("s0", 2),
                    getA2Tuple("s1", 45),
                    getA2Tuple("s2", 0),
                    getA2Tuple("s3", 20),
                    getA2Tuple("s4", 140),
                    getA2Tuple("s5", 6),
                    getA2Tuple("s6", 35),
                    getA2Tuple("s7", 2),
                    getA2Tuple("s8", 0),
                    getA2Tuple("s9", 0)
                ).forEach {
                    lhm.addIfAbsent(it.first, it.second)
                }
            }
        }

        override val stateEnterActionLHM = makeStringActionListLHM().also { lhm ->
            lhm.addIfAbsent("s7", arrayListOf(makeAssignAction("r") { it + 1 }))
        }

        val tConstraintGetter = makeConstraintGetter(
            "t",
            equal_name_StrStrBoolFun,
        ) { (oldLs, oldConstraint, timeEventChoiceArr) ->
            val oldD1D2 = oldConstraint.getD1D2Pair()
            val time = timeEventChoiceArr[0].time
            val d1 = oldD1D2.d1?.let { it + time }
            require(oldConstraint is ZoneConstraint)
            makeZoneConstraint(
                oldConstraint.cloneCVs(),
                oldConstraint.getIfEq1(),
                d1,
                d1,
                oldConstraint.getIfEq2(),
            )
        }

        val cConstraintGetter = makeConstraintGetter(
            "r",
            equal_name_StrStrBoolFun,
        ) { (oldLs, oldConstraint, timeEventChoiceArr, newLocations) ->
            val varName = "r"
            var d1 = oldConstraint.getPointD()
            val newState = newLocations.first()
            val actions = stateEnterActionLHM[newState]
            if (actions != null) {
                val action0 = actions[0]
                if (action0 is AssignAction) {
                    val assignFunTuple = action0.assignFunTuple
                    if (assignFunTuple is DoubleDoubleFunTuple) {
                        d1 = assignFunTuple.theFun.invoke(d1)
                    }
                }
            }
            makePointConstraint(varName, d1)
        }

        override fun updateConstraint(
            exitStateList: ArrayList<String>,
            oldConfig: Config,
            newConfig: Config,
            timedEventList: List<TimedEvent>,
        ) {
            val newCs = ArrayList<Constraint>()
            listOf(
                tConstraintGetter,
                cConstraintGetter,
            ).forEach {
                val newC: Constraint = it.getNewConstraint(
                    makeConstraintGetterArg(
                        oldConfig.states,
                        newConfig,
                        timedEventList,
                        newConfig.states,
                    )
                )
                newCs.add(newC)
            }
            newCs.forEach {
                newConfig.varConstraintLHM[it.getVariablesString()] = it
            }
        }

        override fun getInitialConfig(debuggerList: DebuggerList): Config {
            return makeConfig(
                "s0",
                makeZoneConstraint(
                    arrayListOf(
                        makeCoeffVar(1.0, "t"),
                    ),
                    true,
                    0.0,
                    2.0,
                    true,
                ),
                makePointConstraint("r", 0.0),
            )
        }

        override val aStateNoteRunResultFunLHM by lazy {
            makeA2LHM<String, NoteRunResultFun>().also { lhm ->
                listOf("s0", "s7").forEach {
                    lhm[it] = txNoteRunResultFun
                }
            }
        }

        override fun isBadEnd(nowConfig: Config): Boolean {
            val nowT = nowConfig.byNameFindC(tClock).getPointD()
            return !isOnFinalLocation(nowConfig) || nowT > timeNeedEnd
        }

        override fun doIfBadEnd(nowConfig: Config, debuggerList: DebuggerList) {
            nowConfig.byNameFindC(tClock).setPointD(timeIfBad)
        }

        override fun noteRunResultWhenEnd(nowConfig: Config, runResult: RunResult, debuggerList: DebuggerList) {
            if (isBadEnd(nowConfig)) {
                doIfBadEnd(nowConfig, debuggerList)
            }
            nowConfig.states.forEach {
                runResult.endStateList.add(it)
            }
            super.noteRunResultWhenEnd(nowConfig, runResult, debuggerList)
        }

        override val allVars: List<String> by lazy {
            listOf(tClock, "r")
        }
    }

    object StrategyObj {
        const val aStart = "s0"
        const val aBike = "s1"
        const val aCar = "s2"
        const val aCarEasy = "s3"
        const val aCarHeavy = "s4"
        const val aTrain = "s5"
        const val aTrainGo = "s6"
        const val aTrainWait = "s7"
        const val aGoBack = "s8"
        const val aEnd = "s9"

        const val bike = "s0s1"
        const val bike_end = "s1s9"
        const val car = "s0s2"
        const val carEasy = "s2s3"
        const val carHeavy = "s2s4"
        const val carEasyEnd = "s3s9"
        const val carHeavyEnd = "s4s9"
        const val train = "s0s5"
        const val trainGo = "s5s6"
        const val trainGoEnd = "s6s9"
        const val trainWait = "s5s7"
        const val trainWaitTrain = "s7s5"
        const val trainWaitBack = "s7s8"
        const val backStart = "s8s0"

        fun SubT2SNodeUnit.addBikeEnvStrategy() {
            addSNode(
                makeKREWTuple(aBike, 42..45, bike_end, 1.0),
            )
        }

        fun SubT2SNodeUnit.addBaseCarEnvStrategy() {
            addSNode(
                makeKREWTuple(aCarEasy, 20.toIntRange(), carEasyEnd, 1.0),
                makeKREWTuple(aCarHeavy, 140.toIntRange(), carHeavyEnd, 1.0),
            )
        }

        fun SubT2SNodeUnit.addCarEnvStrategy() {
            addBaseCarEnvStrategy()
            addSNode(
                makeKREWTuple(aCar, 0.toIntRange(), carEasy, 10.0),
                makeKREWTuple(aCar, 0.toIntRange(), carHeavy, 1.0),
            )
        }

        fun SubT2SNodeUnit.addCarEasyEnvStrategy() {
            addBaseCarEnvStrategy()
            addSNode(
                makeKREWTuple(aCar, 0.toIntRange(), carEasy, 10.0),
            )
        }

        fun SubT2SNodeUnit.addCarHeavyEnvStrategy() {
            addBaseCarEnvStrategy()
            addSNode(
                makeKREWTuple(aCar, 0.toIntRange(), carHeavy, 1.0),
            )
        }

        fun SubT2SNodeUnit.addBaseTrainEnvStrategy() {
            addSNode(
                makeKREWTuple(aTrainGo, 35.toIntRange(), trainGoEnd, 10.0),
                makeKREWTuple(aGoBack, 0.toIntRange(), backStart, 1.0),
            )
        }

        fun SubT2SNodeUnit.addTrainEnvStrategy() {
            addBaseTrainEnvStrategy()
            addSNode(
                makeKREWTuple(aTrain, 4..6, trainGo, 10.0),
                makeKREWTuple(aTrain, 4..6, trainWait, 1.0),
            )
        }

        fun SubT2SNodeUnit.addSimpleTrainEnvStrategy() {
            addBaseTrainEnvStrategy()
            addSNode(
                makeKREWTuple(aTrain, 5.toIntRange(), trainGo, 10.0),
                makeKREWTuple(aTrain, 5.toIntRange(), trainWait, 1.0),
            )
        }

        //环境策略实例
        fun getT1EnvStrategy(): SubT2SNodeUnit {
            return makeSubT2SNodeUnit().also {
                it.addBikeEnvStrategy()
                it.addCarEnvStrategy()
                it.addTrainEnvStrategy()
            }
        }

        fun getSimpleT1EnvStrategy(): SubT2SNodeUnit {
            return makeSubT2SNodeUnit().also {
                it.addBikeEnvStrategy()
                it.addCarEnvStrategy()
                it.addSimpleTrainEnvStrategy()
            }
        }

        fun getCarEasyEnvStrategy(): SubT2SNodeUnit {
            return makeSubT2SNodeUnit().also {
                it.addBikeEnvStrategy()
                it.addCarEasyEnvStrategy()
                it.addTrainEnvStrategy()
            }
        }

        fun getCarHeavyEnvStrategy(): SubT2SNodeUnit {
            return makeSubT2SNodeUnit().also {
                it.addBikeEnvStrategy()
                it.addCarHeavyEnvStrategy()
                it.addTrainEnvStrategy()
            }
        }

        //人策略
        object Ren {
            val aUniformRenStrategy by lazy {
                makeSubT2SNodeUnit().also {
                    it.addSNode(
                        makeKREWTuple(aStart, 0..2, bike, 1.0),
                        makeKREWTuple(aStart, 0..2, car, 1.0),
                        makeKREWTuple(aStart, 0..2, train, 1.0),
                        makeKREWTuple(aTrainWait, 0..2, trainWaitTrain, 1.0),
                        makeKREWTuple(aTrainWait, 0..2, trainWaitBack, 1.0),
                    )
                }
            }

            fun getUniformCs(
                locationString: String,
            ): A2LHM<ITimedEvent, Double> {
                val sNode = aUniformRenStrategy.relationLHM[null, locationString]
                require(sNode is LeafSNode)
                return sNode.eventDPLHM
            }
        }

        object ER {
            val aUniformERStrategy by lazy {
                makeL0ERT2SNodeUnit(
                    getSimpleT1EnvStrategy(),
                    aUniformRenStrategy,
                )
            }
        }
    }

    object CalObj {
        class D2DFun(val aFun: (Double, Double) -> Double) : IDsDFun {
            companion object {
                fun makeD2DFun(aFun: (Double, Double) -> Double) = D2DFun(aFun)
            }

            override fun aDsToString(vararg ds: Double): Double {
                return aFun(ds[0], ds[1])
            }
        }
    }

    object TrafficStrategyEx : IStrategyEx<T2SNodeUnit> {
        fun makeStrategy(ren: SubT2SNodeUnit): T2SNodeUnit {
            return makeL0ERT2SNodeUnit(getSimpleT1EnvStrategy(), ren)
        }

        override fun makeNewStrategy(): T2SNodeUnit {
            return makeStrategy(
                ren = makeSubT2SNodeUnit()
            )
        }

        override fun makeFixedStrategy(): T2SNodeUnit {
            return makeStrategy(
                ren = aUniformRenStrategy,
            )
        }
    }
}